Skip to content

Release v2.1.0: the production run, its artifacts, and the release-engineering repairs — MERGE COMMIT ONLY, never squash - #3

Merged
tlancaster6 merged 458 commits into
mainfrom
results/rerun-freeze-02
Aug 27, 2026
Merged

Release v2.1.0: the production run, its artifacts, and the release-engineering repairs — MERGE COMMIT ONLY, never squash#3
tlancaster6 merged 458 commits into
mainfrom
results/rerun-freeze-02

Conversation

@tlancaster6

Copy link
Copy Markdown
Collaborator

Verdicts first

This repository has never had a pull request, so there is no house template to match. The shape used
here is the one this project already uses for its rulings — 29.1-PREPUSH-AUDIT.md — where the
bolded verdicts sit at the very top, before any evidence. The ordering is deliberate: a reader
who takes only one thing from this page must take the merge-method instruction, and that instruction
is worthless if it is below the fold.

Verdict on what this merges: the full production run of AquaCal, its committed artifacts, and
every library change from the phases that produced them.
451 commits ahead of main, re-derived
at the moment this request was opened.

Verdict on the merge method: THIS MUST BE MERGED WITH A MERGE COMMIT. NEVER A SQUASH. A squash
collapses all 451 commits into a single message, and python-semantic-release parses only that one
message — so the release silently does not happen. See § The one instruction that cannot be got
wrong
.

Verdict on the branch and tag: decline the "Delete branch" button after merging. Both the branch
and the tag are kept on purpose. See § The second instruction.

Verdict on readiness: the three CI repairs this branch needed have landed and were each measured
locally before this request was opened.
CI's whole-tree hook sweep was run once, locally, on a
committed-clean tree and moved not one byte; pytest -m "not slow" reported 0 failed; all seven
--smoke invocations exited 0. The check board on this page is the confirmation on the real
runners. See § Evidence.

Approval status: NOT approved, NOT merged, NOT auto-merged. This request was created and nothing
else. The merge is the author's act, and it is gated behind a separate decision checkpoint
(plan 29.2-05).


What this merges

results/rerun-freeze-02main, at cf14d0e.

Commit count, re-derived rather than quoted. The roadmap's figure for this branch is stale and is
carried here rather than silently replaced, because the count has drifted twice already:

Source Commits ahead of main
Re-derived at PR-creation time (git rev-list --count origin/main..results/rerun-freeze-02) 451
Re-derived during the 29.2-01 release rehearsal, 2026-08-27 438
RESEARCH, 2026-08-26 429 / 431
ROADMAP (stale) 406

No number above was adjusted toward another. The drift is in .planning/ documentation commits; the
conventional-commit type counts that determine the version bump are unaffected — 47 feat, 30 fix,
zero breaking-change markers, which is a minor bump to 2.1.0.

The branch contains:

  • The full production experiment suite run, executed end to end on the Linux run machine, and its
    committed artifacts — the 227-file provenance commit 70e783f, the graded gate results, the
    real-rig metrics, the interface-ablation band, and the driver-state record of both run attempts.
  • Every library change from the phases that produced those artifacts — the calibration and
    optimisation work under src/, its tests, and the experiment drivers under experiments/.
  • This phase's release-engineering repairs: an exact pin of the release toolchain in
    release.yml, the two pre-commit byte-integrity excludes, and one numeric test assertion
    converted from exact equality to a bounded ULP step count.

What must happen after the merge, and in what order

  1. The merge commit lands on main. release.yml fires on push to main.
  2. python-semantic-release parses the merge commit's message, computes a minor bump, cuts
    v2.1.0, bumps both version files — pyproject.toml (version_toml) and CITATION.cff
    (version_variables) — writes the CHANGELOG.md section, commits it as chore(release):, and
    pushes an annotated v2.1.0 tag.
  3. The tag fires publish.yml, which builds the sdist and the wheel, publishes to TestPyPI, and
    then stops at a human approval gate on the pypi environment before it can reach the real
    package index. Nothing reaches PyPI without someone pressing approve.

All of step 2 and the build half of step 3 have already been executed once, against a throwaway
clone of this repository with its origin re-pointed at a local bare repo, so that nothing could
escape. That rehearsal produced: the chore(release): commit, the annotated v2.1.0 tag, both
version files bumped to 2.1.0, the generated changelog section, and both distributions
(aquacal-2.1.0.tar.gz and its wheel) with the sdist's top-level member set asserted against a
positive whitelist. The next-version string was asserted string-equal to 2.1.0, with MAJOR = 0
straight from the release tool's own parser.

The rehearsal also produced the reason release.yml is pinned on this branch: no released
python-semantic-release works with the current GitPython, so the workflow now installs
python-semantic-release==10.6.1 with gitpython==3.1.59, with the measured cause and the
retirement condition written directly at the constant.


The one instruction that cannot be got wrong

Merge this with "Create a merge commit". Never "Squash and merge".

The consequence, in one sentence: a squash collapses every one of the 451 commits into a single
message, and python-semantic-release parses only that message — so if it reads as docs: or
anything else non-releasing, no release fires at all, and the failure is silent.

This repository has all three merge methods enabled (mergeCommitAllowed, squashMergeAllowed,
rebaseMergeAllowed are all true), so the green button is a split button whose default is whatever
method was used last in this browser
. That is the trap. The method must be positively selected,
not accepted.

If you prefer the unambiguous route, the CLI form cannot be overridden by a remembered default:

gh pr merge <N> --merge

Verification afterwards — two parents is the definitive test that a merge commit, not a squash,
landed:

git fetch origin && git cat-file -p origin/main | grep -c '^parent'   # expect 2

The second instruction: decline the delete-branch prompt

After merging, GitHub offers a "Delete branch" button. Decline it.

Both results/rerun-freeze-02 and the v2.1.0 tag are kept deliberately. The tag names the frozen
sha that the published Zenodo record cites, and the branch-and-tag pair is the record of the two run
attempts — deleting the branch would leave the second attempt's history reachable only through the
merge commit.

This is a matter of declining a button, not of changing a setting: the repository's
deleteBranchOnMerge is already false, so nothing is deleted automatically.


Evidence

The four evidence files this phase has produced, each measured rather than asserted:

File What it establishes
29.2-precommit-allfiles-proof.txt CI's pre-commit run --all-files sweep, run once locally on a committed-clean tree, asserted against four independent invariants: exit 0, unchanged working-tree status, both md5 anchors unmoved, and the driver-state TSV still five-field on every line
29.2-release-rehearsal.txt The whole release, executed against a throwaway clone: next version string-equal to 2.1.0, MAJOR = 0, the tag, both bumped version files, the changelog, and the toolchain pin's measured cause
29.2-sdist-listing.txt The sdist's top-level member set, asserted as a positive whitelist under LC_ALL=C, with size demoted to corroboration
29.2-smoke-runs.txt The first measurement of CI's experiments-smoke job against this branch: 7/7 invocations exit 0, experiments/ byte-unchanged

Pre-measured locally, on exactly this tree, before this request existed:

  • pre-commit run --all-files → exit 0, all seven hooks Passed, git status --porcelain -uall
    0 lines before and after.
  • pytest -m "not slow" — the selection CI's four test matrix cells run → 0 failed
    (2326 passed, 21 skipped).
  • The full suite, including slow tests → exactly 2 failed (2395 passed). Both are
    @pytest.mark.slow and are therefore not in CI's selection. They are a known, ruled-on
    platform-pinned exact-equality pair carried forward to Phase 30; two is the expected count, and
    zero or three would each be an anomaly.
  • All seven --smoke experiment invocations → exit 0, experiments/ byte-unchanged.

What this pull request does not do

Created only. Nothing was merged, auto-merged, approved, re-run or cancelled, and no review was
requested that would auto-merge on approval. The merge is the author's act.

The dry-run seam substitutes the ENTIRE command, so it proves sequencing,
resume and gate wiring but can never prove a stage's invocation line is
correct. A typo like --out vs --output passed every dry-run test and would
have failed hours into the frozen 22-31 h run. --smoke makes the real
invocation lines executable in minutes.

- `--smoke` / `SUITE_SMOKE=1` selects the reduced-scale pass. Documented in
  usage() under its own heading: it is NOT a pre-flight override (it disables
  no refusal), which is the fourth exception to the parser's preamble rule.
- `_smoke_args` is the single place the flag is decided; one invocation line
  per stage varies by it, so the full-scale path is provably unchanged.
  16 dispatched lines carry it, verified at the argparse level rather than
  from docs.
- `_record_dispatch` records the argv a stage would launch, under the seam,
  so the invocation lines themselves become assertable without running an
  experiment.
- OUT_DIR forces experiments/results_smoke. Mandatory, not tidy: every
  experiment's --smoke path branches on args.out == parser.get_default("out")
  and that default IS experiments/results (_io.py:64), so passing it is
  indistinguishable from passing nothing.
- Every sibling out dir moves with it. run_stage_e2_band opens with
  `rm -rf "${OUT_DIR_E2_BAND}"`, so a rehearsal left at production values
  would have DELETED experiments/results_e2_band -- three 48-87 min
  calibrations -- as its first act.
- --profile still selects only the completeness gate's expectation profile;
  smoke changes its DEFAULT only, and --profile full --smoke is honored.
- e7_focal_standoff and e4_repeat are SKIPPED with DECLARED REDUCTION lines,
  not failures: the first does nothing with the flag and reads a hardcoded
  experiments/results path; the second's --cell and --splice-repeat both
  refuse --smoke. Both are full-profile-only in the manifest already.
- 26-07's rationale stands and is restated in the banner: every ACCEPTANCE
  and PRODUCTION run is at full scale, never substituted. This pass is not
  evidence about geometry, convergence or any published number.

experiments/suite_expectations.json is untouched.
The dry-run seam substitutes the whole command, so until now no test could
see a stage's argv -- a mistyped flag passed all 20 dry-run tests and all
113 unit tests, then failed hours into the frozen run. These read the REAL
dispatched invocation lines via the driver's `_record_dispatch`, still
without running a single experiment.

TestSmokeMode (8 tests)
- the flag lands on exactly the 15 observable supporting stages, and on no
  stage without it
- e7_focal_standoff and e4_repeat are skipped, DECLARED, and not failures --
  all three asserted, since any one alone passes on a broken driver
- no dispatch names experiments/results. Compared token-by-token, never by
  substring: results_smoke and results_e2_timing both CONTAIN it, so a
  substring check is satisfied by the very bug it must catch
- no dispatch names a production sibling tree either. run_stage_e2_band
  opens with `rm -rf "${OUT_DIR_E2_BAND}"`, so a rehearsal at production
  values would delete three 48-87 min calibrations as its first act
- the banner says the pass is not evidence, at launch AND in the summary
- SUITE_SMOKE=1 is asserted equivalent on the ARGV, not on a log line

TestSmokeAndProfileStaySeparable (3 tests)
- the conflation that blocked 26-10: --profile selects the gate's
  expectations, --smoke selects the scale. `--profile full --smoke` honored.

TestFullScalePathDidNotMove (4 tests)
- a frozen 17-line snapshot of the full-scale dispatch list, plus a coverage
  check against the manifest so the snapshot cannot pass by capturing
  nothing. This is what makes "production is untouched" checkable rather
  than asserted.

run_driver gains `sandbox_out=False` for the tests that assert WHICH tree
the driver resolves -- a sandboxed SUITE_OUT_DIR overrides that resolution
and would make those assertions vacuous. 26-08's explicit-timeout convention
is unchanged: no new subprocess.run was added.

35 pass in 155 s.
Records three auto-fixed deviations, the sharpest being that a smoke pass
at production sibling-dir values would have deleted experiments/results_e2_band
via run_stage_e2_band's opening rm -rf. Also records why the plan's 'exactly 8
stages' criterion was a scripts-vs-stages category error, and the measured
counts that replaced it.
e3 consumes E2's benchmark.json through a hardcoded, cwd-relative path
(e3_derived_quantities.py:173) that --out does not redirect, but declared
depends_on: [preflight] and ran at stage 3 -- five stages before
e2_production writes that file.

This was invisible for as long as experiments/results still held a previous
run's copy. 26-09's archive-aside emptied the tree, and the 26-10 smoke pass
then died on `ValueError: cannot convert float NaN to integer` twice in a
row, losing structural_scaling.csv, cpr_grouping.tex and
cpr_derived_values.tex. In the frozen run that is a stage-3 crash whose
recovery protocol is restart-from-stage-1.

The edge moves e3 to level 2, shortest-first within it. This also subsumes
the deliberate prelaunch_probe/e3 inversion the old comment recorded: the
dependency now enforces that ordering rather than a hand-placed exception.

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
build_derived_values_latex_content cast the 13cam/200frame row's n_params
with a bare int(). When E2's benchmark.json is absent the CPR path already
degrades that row to nulls and stamps record_source=missing_e2_benchmark --
so the cast hit NaN and took the whole stage down, losing
structural_scaling.csv as collateral. That is what killed e3 twice in the
26-10 smoke pass.

The guard renders a deliberately NON-NUMERIC marker, "N/A (benchmark record
absent)". Not a zero and not a stale count: either could be read as a
measurement if the fragment reached the manuscript. Exit stays 0 because
--skip-e2 declares a legitimately synthetic-only run, and failing the stage
there would cost e3's four other artifacts for no gain.

Verified byte-identical output against the committed
pre_rerun_baseline/results/cpr_derived_values.tex on the present-record path.

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
gate3_provenance reads record["seed"] or solver_config["seed"]
(check_rerun_gates.py:374-378). E1's three and E7's two
write_direct_call_benchmark call sites never passed one, so all six gated
records failed the gate in every run -- including the 26-10 smoke pass, on
records it had just written. Six unconditional FAILs would have made Phase
29's RUN-03 ("gates pass") unsatisfiable and turned the frozen run's gate
output into a list needing hand-triage.

The writer has accepted seed= since 19.2-02; only the call sites were
missing. E4 and E6 already passed it, which is why the failing set was
exactly six.

Band paths pass seeds[-1]: the record already reflects the LAST seed's
diagnostics/timings/accuracy by design, so that is the seed it is labelled
with, while solver_config["seeds"] keeps the full swept list. Both modules'
docstrings are updated -- E1's previously said these records stay seedless,
which is the behavior this commit deliberately changes.

Also corrects the SEEDLESS_LEGACY_RECORDS comment, which claimed the
exemption lifts "the moment any of these six is regenerated". It never did:
no call site passed a seed, so regeneration changed nothing. The named six
are the archived pre_rerun_baseline artifacts and stay exempt because they
will never be rewritten.

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
Both defects were surfaced by the 26-10 smoke pass and both would have hit
the frozen run, not just the reduced-scale one:

26-12 -- e3 read E2's benchmark.json five stages before e2_production wrote
it, crashing stage 3 on int(NaN).

26-13 -- E1's and E7's call sites never passed a seed, so gate3_provenance
failed on six records unconditionally, making Phase 29's RUN-03 unsatisfiable.

Both summaries record a deviation the next reader should see: 26-12 removed
a deliberate stage-ordering inversion that the new dependency edge subsumes,
and 26-13 reverses an E1 docstring sentence that said band-written records
must stay seedless.

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
DRIVER-04 emptied experiments/results/ and 26-01 (e3a7bf3) repointed four
test modules at experiments/pre_rerun_baseline/results/. Correct at the time
-- with the live tree empty, the archive IS the subject. But nothing pointed
them back, so after Phase 28 repopulates the live tree, every provenance,
schema and exhaustiveness rail in those modules would pass green while
validating history rather than the frozen run's output. Same shape as the
decision-coverage gate that passed while parsing nothing.

tests/unit/_baseline_paths.py decides live-vs-archive in one place: prefer
the live tree once it HOLDS A FILE (26-09's move leaves it present and
empty, so existence is the wrong test), else the archive.

The four modules do not all want the same treatment, and that is the point:

- provenance: resolver + a RESULTS_TREE marker that names the resolved tree
  in skip reasons. Its gates assert properties of whatever tree is current.
- e3: baseline_file(). BENCHMARK_JSON_PATH is INPUT to build_cpr_grouping_df.
- e5: baseline_file(). compute_scale_bias is checked AGAINST committed data,
  so fresher data is a stronger check.
- io: archive_results_dir(), pinned. test_e1_committed_record_has_no_seed_key
  asserts the committed record has NO seed -- and 26-13 just made the live
  one carry one, so following the resolver would invert it.

The two SEEDLESS_LEGACY_RECORDS tests are pinned to the archive for the same
reason, and the carve-out itself now applies only when the resolved tree IS
the archive: against a live tree no record is exempt, which 26-13 made true
at write time.

Also tightens TestDefaultMetricsPathAnchoring. 26-01's docstring said it
"re-tightens on its own once Phase 28's run repopulates experiments/results/"
-- it did not: the resolved-or-archived disjunction would have stayed
permissive forever, letting the archive rescue a broken production path
indefinitely. The fallback is now allowed only while the live tree is empty.

No production constant is repointed; that stays D-12's --baseline-dir work.

Four modules: 412 passed / 25 skipped before, 414 / 25 after -- the +2 are
this plan's own observability tests, skips unchanged.

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
…s closed

26-10's D-33 form-1 acceptance pass ran all 20 stages end to end at 88512b7
in 11m05s. 19 of 20 stages exit 0; the roll-up is 71 PASS / 9 N/A / 12 FAIL,
down from 65/18 before the fixes -- six FAILs cleared, none appeared.

The three gap-closure plans that pass produced:

  26-12  e3 ran five stages before e2_production wrote the record it reads
  26-13  E1/E7 records carried no seed; gate3_provenance failed on six
  26-14  the baseline test rails were pinned to the archive for good

Full suite at close: 2190 passed, 25 skipped, 0 failed (1:08:55).

26-10-SUMMARY records what the pass does NOT prove, which matters more than
what it does: under --smoke, OUT_DIR is results_smoke while e3's hardcoded
read points at experiments/results/, so the pass cannot demonstrate 26-12's
dependency edge at all -- e3 exits 0 there only because of the NaN guard. The
edge's evidence is the topological test and the manifest diff.

It also corrects a claim made during the session: structural_scaling.csv was
read as collateral of e3's crash, but _write_tier4 is full-run only and the
smoke path never writes it. The crash does cost that artifact on the full
path; the smoke evidence never showed it.

Three items left open deliberately at close, all recorded in STATE.md: the
smoke-profile artifact mismatch (a smoke pass can never exit 0), the resume
gap the user deferred, and reconstruction_bootstrap's hardcoded input path.

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
… waves

Phase 27 (RUN-01) freezes the library, driver, gates and environment at one
sha and proves it runnable on the Linux target before the run.

Wave 1 (7 parallel plans): target reconnaissance; the two driver defects
(path-kind-agnostic pre-flight for the image set, exit-code-aware resume); a
truthful smoke profile plus the Phase 25 registration finding; --out-honest
reconstruction_bootstrap; the lockfile emitter and the two-regime thread
record; the emitter-coverage report; the frozen-row note.

Wave 2: the committed Linux E2 release config, both Windows literals as
detect-then-fallback, the concurrent-stages-only thread pin, and HANDOFF.md.

Waves 3-6 are serial by construction: local smoke acceptance plus the
pre-push audit, then the push and the non-v* tag, then the on-target clone
and dry run, then the short real stage, the gate roll-up, on-target smoke and
the close-or-refreeze loop.

Findings folded into the plans:
- The 12 smoke FAILs reproduce exactly over the preserved smoke tree and
  decompose 3 completeness / 2 check_e5 / 2 check_e4 / 4 check_e6 / 1
  check_e6_seed_band -- so D-20 class 3 is already satisfied at the manifest
  and its residual FAILs come from the per-experiment checker.
- experiments/results_smoke/ is gitignored, so worktree executors must point
  the gate at the main working tree.
- experiments/configs/ is the only safe home for the committed E2 config:
  at experiments/ the invocation-config generator's release-tree write
  refusal would fire against its own default target.
- The dry-run seam is RUN_EXPERIMENT_SUITE_DRY_RUN, not the name CONTEXT.md
  carries.

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
…D-13)

- header must name sys.executable, the Python version, sys.version and a UTC stamp
- a failed pip freeze must still write a file, with a reason, and still exit 0
- FileExistsError without --force; overwrite with it

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
…solution

- resolver returns native / default-tree / (None, reason) branches (D-23)
- absent companion degrades to None, never a false negative
- --check reports 'not comparable' rather than a mismatch
- --smoke under a non-default --out exits 0
- 13 extrinsic dirs x 262 files, 3,799,096,889 B total
- byte total DOES NOT clear min_total_bytes: 4000000000 (5.02% short)
- conda env is lowercase 'aquacal'; bare 'python' absent on target
- 662 GiB free, 31.06 GiB RAM, 32 cores -- SUITE_WORKERS 4-5 confirmed
…D-20 class 1)

- structural_scaling.csv, e5_provenance.json and fd_jacobian_accuracy.json
  were tagged ["smoke","full"], but no --smoke code path writes them:
  e3_derived_quantities.py:1106-1126 returns before _write_tier4, and the
  _run_smoke paths of e5_index_sensitivity.py and fd_jacobian_accuracy.py
  return before their sidecar writes. Each retag carries that file:line as
  its rationale.
- fd_jacobian_accuracy.csv IS written under smoke and stays ["smoke","full"].
- benchmark_grid.csv was already ["full"] and is untouched.
- Two TestProfiles cases guard the retag against a silent revert.
- Smoke roll-up over the preserved 26-10 tree: 12 FAIL -> 9 FAIL.
…-09)

- lift the frameset probe out of the driver source and run it directly; the
  dry-run seam stubs the whole pre-flight stage, so run_driver cannot reach it
- a 13-directory image set, a 13-file video set, absence, a sub-floor byte sum
  and a partially present frameset, for both path kinds
- the three directory cases fail today: p.is_file() is False for a directory,
  so present is empty and the probe exits 2 = ABSENT
- three of four assumptions came back false, all in 27-08's scope
- byte floor, GATE_PYTHON chain and relative config paths all need work
- `python -m experiments._env_lock --out <dir>` writes environment_lock.txt
- header names sys.executable, python_version, sys.version, platform, a UTC
  stamp and the OpenBLAS build read from numpy.show_config(mode="dicts")
- body is `sys.executable -m pip freeze`, so the lock describes the interpreter
  running the suite, not whatever pip is first on PATH (D-28)
- a failed freeze writes the reason and still exits 0: the lock is an artifact,
  never a fourth pre-flight refusal (P26-D-50)
- pyproject.toml and requirements.txt are deliberately untouched

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
- presence is p.exists(), so a directory of frames counts as present
- a directory is sized by walking it; a directory's own st_size is meaningless
  and is never summed. A video file is still sized directly
- both assertions, both manifest reads and all three exit codes are unchanged,
  so ABSENT (2 -> --skip-e2) and MISMATCH (3 -> --allow-frameset-mismatch) stay
  distinct refusals with distinct overrides
- the header comment now records why: the target holds an IMAGE set and
  detection.py already auto-selects ImageSet for a directory
- cap read from SUITE_THREAD_CAP; unset is None-present, never absent
- unpinned list is exactly the four serial_alone timing stages
- both lists derived from suite_expectations.json, disjoint, covering
- a missing or unparseable expectations manifest degrades to None
- the new keys stay out of REQUIRED_MANIFEST_FIELDS (still 17)

Claude-Session: https://claude.ai/code/session_011CF4fTGkFA75QgxYkmVDsk
…(D-23)

- REAL_RIG_METRICS_PATH is now __file__-anchored, matching E2_BENCHMARK_PATH
- new resolve_real_rig_metrics_path: native / default-tree / (None, reason),
  mirroring e4_benchmark_grid.py:296-309; never imports across trees
- _run takes out_dir; absence sets the cross-check to None and records
  real_rig_metrics_resolution in the artifact
- --check reports 'not comparable' instead of a false-negative mismatch
- the stage now exits 0 under --smoke instead of FileNotFoundError
…-20)

Covers every case in the plan's behavior block: N/A at smoke for the
absent benchmark_grid.csv and e5_provenance.json, N/A for E6's
gate4_optimality on a collapsed solve, a non-FAIL cameras_axis verdict on
a collapsed band -- each paired with its profile='full' / profile=None
regression, plus the ValueError-on-unknown-profile contract and the
assertion that a suppressed gate stays VISIBLE (T-27-03-01).

RED: 22 failed, 5 passed.
tlancaster6 and others added 29 commits August 26, 2026 17:40
…ython pair

- release.yml now installs python-semantic-release==10.6.1 and
  gitpython==3.1.59 exactly, instead of resolving whatever is latest at
  merge time
- GitPython 3.1.60 (2026-08-25) removed Actor.name_email_regex; PSR reads it
  unconditionally at cli/config.py:745, so every subcommand dies. No released
  PSR works with current GitPython, so both packages must be named
- rationale block sits at the constant per the .pre-commit-config.yaml
  convention, with the retirement condition (upstream 1475/1476, PR 1477)
- records that no other workflow in this repo pins anything, so the divergence
  from house style is deliberate
…waway clone

- semantic-release version --print, run on a branch literally named main after a
  --no-ff merge of results/rerun-freeze-02, printed exactly "2.1.0" (5 bytes,
  od -c confirmed). D-29.2-20 settled: the parser reports MAJOR = 0
- produced the release commit b411449, an annotated v2.1.0 tag, both version
  files at 2.1.0, a 1,219-line CHANGELOG section and the sdist
- re-derived D-29.2-19's figures: 438 commits ahead (ROADMAP says 406, RESEARCH
  measured 429/431), 47 feat, 30 fix, 0 breaking markers. All three carried
- three measurements the plan and the research did not have: semantic-release
  --version does NOT detect Finding 1; nor does version --print on a
  non-release branch; and the next PR number is #3, not #1
- the first invocation aborted at build_command, reproducing RESEARCH Q6f's
  half-way abort exactly. Cause was the rehearsal harness's PATH, not the repo
  or release.yml. Transcribed rather than hidden
- everything ran in /tmp against a local bare remote; the working repository's
  porcelain and branch list are byte-identical before and after
…series

- the distinct top-level member set string-equals exactly LICENSE, PKG-INFO,
  README.md, pyproject.toml, setup.cfg, src -- a positive whitelist, asserted
  against PSR's own build_command output, before any upload path exists
- 72 members, 66 under src/, zero matching experiments/ .planning/ tests/ docs/
- size series recorded as corroboration with its explanation: 120,434 ->
  174,448 -> 200,871 B, monotonic real src/ growth. States plainly that
  D-29.2-17's literal "in line with 1.8.0's" would have false-alarmed
- two new measurements strengthen D-29.2-32: the sdist is NOT byte-reproducible
  (two builds of the identical tree 20s apart differ by 20 bytes, identical
  member lists), and RESEARCH's 200,937 differs by 66 B for the same reason
  plus setuptools drift. Both figures printed
- records that there is no MANIFEST.in and no file-finder plugin, and that
  adding setuptools-scm without a prune would publish all 1,706 tracked files
- pins LC_ALL=C: the six-name sort order is collation-dependent and an unpinned
  sort would false-alarm on an en_US.UTF-8 machine
…facts

- trailing-whitespace's one-arm ^\.planning/ is replaced by, and
  end-of-file-fixer gains, the corrected single-regex exclude from
  D-29.2-02b -- verified to leave zero leftovers for either hook across
  all 1,693 tracked files
- adds the driver_state/ and run_experiment_suite_state. arms the
  superseded mirrored pattern omitted: the suite-state TSV's 20
  tab-terminated lines are a load-bearing empty fifth column, and the
  four stage logs' padding is column structure (185 of 723 lines in
  e2_band.log)
- rationale block records the measurements, justifies each arm by name,
  gives each a retirement condition, and corrects the framing that had
  the two md5 anchors as the bytes at risk -- they were always covered
- hook-level only; no top-level exclude, ruff entry untouched
…ost down

- the third failing hook exits 123 on ten findings, all under .planning/:
  nine quoted 40-hex frozen commit shas in plan prose plus one audit-table
  row that documents a credential form and counts zero of them
- extends the existing alternation rather than rewriting it; the three
  original arms and the --baseline argument are untouched, and
  .secrets.baseline is not regenerated (D-29.2-27, author ruling 2026-08-26)
- the rationale is folded into the hook's existing block because it reuses
  that block's own argument: a baseline entry cannot hold when every phase
  lands new shas, and the current baseline is 2026-07-28, six files, zero
  planning entries
- the cost is stated at the constant in as many words: the planning tree
  stops being secret-scanned, accepted against this file's own recorded
  lesson that a gate which cries wolf gets bypassed wholesale
…teps

The one D4 member CI's not-slow selection runs now passes, with the reason
written at the assertion rather than in a commit message.

- r_q_m and chord_incidence_deg compare with np.testing.assert_array_max_ulp
  at maxulp=4 and maxulp=8 -- four times the measured worst cases of 1 and 2
  steps over all 22 rows, so the bound is not pinned to this machine's SIMD
  dispatch. h_c_m and h_q_m keep exact equality: both measured 0/22 mismatches.
- _assert_max_ulp joins numpy's observed-step-count message to the row dict,
  preserving this method's convention that the offending row is printed.
- The docstring states the mechanism -- the library evaluates r_q vectorized
  while this test evaluates it scalar, and numpy's two paths differ by one step
  on the SAME machine -- correcting the D4 todo, which classifies this member
  as a Windows-captured cross-platform anchor. It has no captured anchor.
- 8 steps is 1.110e-16 absolute / 1.04e-15 relative at the measured magnitude,
  ~9 orders of magnitude tighter than the pytest.approx default this must not
  become. Nothing is skipped, xfailed or deselected.
Every check CI's `test` and `experiments-smoke` jobs will run on the release
PR, run here first at 3026813, with exit codes.

- not-slow selection: 0 failed (2326 passed, 21 skipped, 71 deselected, exit 0).
  This is the command four CI cells run; no D4 member can redden one now.
- whole suite: exactly 2 failed (2395 passed, 21 skipped, exit 1), both
  @pytest.mark.slow fixture-constant readers. Collected total is unchanged at
  2418 and passed is exactly one higher than the todo's recorded 2394 -- one
  test moved from failed to passed and nothing else moved.
- all seven --smoke invocations exit 0 in the workflow's own order, 122 s total.
  That job had never been exercised against this branch.
- experiments/ byte-unchanged and both md5 anchors unmoved after the runs,
  measured rather than asserted.

States the superseded invariant: two is now the expected whole-suite count and
zero and three are both anomalies. No prior artifact was rewritten to agree.
The not-slow BEFORE cell is labelled a derivation, not a fresh run.
The third left the set by passing, and the reason is written where the next
reader of the todo will find it rather than inferred from a shrinking list.

- Title, frontmatter file list and the What-fails node ids drop to two. The
  whole-suite count is updated to the measured 2 failed, 2395 passed,
  21 skipped and cites 29.2-smoke-runs.txt section 4 rather than memory.
- A new section records the departure: what was done, why THIS document's
  classification of that member was wrong (it has no captured anchor -- the
  gap was the library's vectorized r_q against the test's scalar r_q on the
  SAME machine, a dispatch asymmetry, not a Windows pin), and why the other
  two are genuinely different and stay.
- The correction is recorded as a correction of this document, not as a defect
  of the phase that wrote it: from the failure list alone the classification
  was reasonable.
- resolves_phase stays at 30. The do-not-silence instruction is preserved in
  substance; only the number it quotes moved.
- Nothing prior was rewritten. The tag message and 29.1-PREPUSH-AUDIT.md still
  say three; the amend names 29.2-PHASE-RECORD.md (plan 29.2-08) as where the
  superseding statement belongs.
Runs `pre-commit run --all-files` once, locally, on a committed-clean tree --
the first execution of that command anywhere in this project, and the first
execution of the exclude patterns 29.2-02 wrote (e671074, d9e8f49).

- exit 0, with all seven per-hook result lines recorded, not only the three
  that were repaired
- `git status --porcelain -uall` 0 lines before and after: nothing rewritten,
  nothing created
- both md5 anchors asserted by string equality against their literals
- the driver-state TSV's distinct field count is the single value 5, the
  invariant named rather than only generically observed

Hook runner installed unpinned (pre-commit 4.6.2) into an isolated venv,
exactly as test.yml:54 does it, so what is proved is what CI runs.
…uest

The push of results/rerun-freeze-02 is refused: the branch carries 7ab969e,
which edits .github/workflows/release.yml, and the available credential holds
'gist, read:org, repo' without 'workflow'. SSH is not configured either, and no
alternative token is present.

Records what was deliberately NOT done: a PR could have been opened from the
already-pushed 2205eeb without any push, but that commit predates all three
wave-one repairs, so its board would be red AND would describe a tree the
sections 1-7 proof does not cover.

The four-assertion proof is unaffected and was re-taken at this sha.
- Author granted the `workflow` scope; the push section 8 recorded as rejected
  now succeeds, and remote == local == cf14d0e, the sha the proof covers
- PR #3 opened base main <- head results/rerun-freeze-02, OPEN and unmerged;
  `mergedAt` and `autoMergeRequest` both null, asserted by string equality
- Body is verdict-first: merge-commit-never-squash with its consequence, the
  decline-deletion instruction, and all four evidence files linked at the sha
- Section 9 supersedes section 8 without editing it (S6)
- Corrects PATTERNS' "no PR has ever been opened" claim: two merged PRs predate
  this one; `--state open` could not see them
- Commit count re-derived live at 451, printed beside 438 / 431 / 406
… job

Sphinx runs with -W --keep-going, so four docstring-formatting messages ended
the Documentation workflow with exit 1. All four are reStructuredText prose
faults inherited from phases 24/25; phase 29.2 touched no src/ file before this
commit. The job triggers only on pull_request into main and last ran 2026-04-20,
which is why the defect surfaced on this PR rather than earlier.

- interface_estimation.py, refinement.py: the two parallel
  DEGENERACY_WARNING_FRACTION_THRESHOLD blocks had an indented bullet list hard
  against the sentence above it, so docutils read the pair as a definition list
  and then hit an unindent with no blank line. Blank lines added either side and
  the bullets dedented to the surrounding column, which is what the prose meant.
- diagnostics.py: save_diagnostic_report's 'Creates:' list had no blank line
  after the lead-in, producing ERROR Unexpected indentation and WARNING Block
  quote ends without a blank line. One blank line added.

Prose only. No value, signature, default or threshold moved -- asserted rather
than promised: with docstrings stripped, all three files' ASTs are byte-identical
to their parents, and DEGENERACY_WARNING_FRACTION_THRESHOLD is still 0.01.

Verified locally through napoleon + docutils, the same pipeline that emitted the
messages: 4 messages before, 0 after, with the two constant line numbers (10, 13)
matching CI verbatim. The full sphinx build could not be reproduced here --
nbsphinx needs pandoc, which is absent and sudo is password-gated -- so CI's
build-docs job is the confirmation.
…and the re-proof

- The board returned SEVEN rows against a predicted six; the surplus row was
  `build-docs` from docs.yml, and it had already failed. T-29.2-17's
  state-the-count-first mitigation caught a surplus job, which is not the
  direction it was designed for
- docs.yml triggers only on pull_request into main and last ran 2026-04-20, so
  like the hook sweep it had never fired against this tree
- All four sphinx problems recorded verbatim; provenance asserted as inherited
  from phases 24/25, not introduced by 29.2 (`git log ... -- src/` empty)
- The author's ruling to repair in-plan recorded as an explicit scope deviation
- Behaviour-neutrality asserted by AST comparison with docstrings stripped
- Local napoleon+docutils verification, 4 before / 0 after, with the limit of
  that check stated: no pandoc, so the full build is CI's to confirm
- The sweep re-run in full at 3ffebe3 with all four invariants re-asserted
…hind it

- 7 rows, 3 pass / 4 fail, every name, state, bucket and link recorded verbatim
- GREEN: pre-commit (ROADMAP criterion 1 CONFIRMED on the real runner in 33s),
  experiments-smoke, and build-docs -- red on the prior sha, green after the
  section-10 repair, which is the notebook-inclusive confirmation the local
  pandoc-less check could not give
- RED: all four test matrix cells, identically -- 5 failed / 2319 passed /
  23 skipped, every one in tests/unit/test_run_manifest.py
- Reported as NEW INFORMATION against 29.2-03's local 0 failed, both tallies
  printed, neither adjusted toward the other
- Diagnosed from the run logs: fetch-depth 1 / fetch-tags false leaves
  `git describe` nothing to anchor to, and psutil sits in the `bench` extra
  while CI installs only `.[dev]`. The green-if-fixed claim is labelled an
  inference, not a measurement
- Second instance of section 10's shape: a gate that fires only on a PR into
  main accumulates latent failures until the first PR
- No repair attempted -- no author ruling exists for this one. Nothing skipped,
  xfailed, deselected, re-run or cancelled
- Unprotected main recorded as context and explicitly not as permission
- Notes the local/PR head divergence that plan 29.2-05 must reconcile
- Also commits the two todos filed against this finding for phase 30
…plan

- STATE.md: 4/8 plans, position entry for 29.2-04, session and metrics rows
- ROADMAP.md: 29.2-04 checked off; criterion 1 annotated MET and CONFIRMED on
  the real runner; progress table 3/8 -> 4/8
- WINDOWS.md: the unrepaired test_run_manifest.py CI failure filed as an open
  defect so it stays visible at ship time
…ssert on

The four `test` matrix cells on PR #3 failed identically -- 5 failed / 2319
passed / 23 skipped, every failure in tests/unit/test_run_manifest.py -- from
two independent CI-environment causes, neither of them a defect in the tests:

- `actions/checkout@v4` defaulted to fetch-depth 1 with fetch-tags false, so
  `git describe --match 'v[0-9]*'` had neither the tag nor the commit graph
  back to it and returned None. That is 4 of the 5 failures.
- psutil sits in the `bench` extra while the job installed only `.[dev]`, so
  cpu_count_logical and ram_total_bytes degraded to None exactly as
  src/aquacal/io/benchmark.py:151-159 documents. That is the 5th.

Both are fixed at the job, not at the assertions. Nothing was skipped,
xfailed, deselected or relaxed.

Verified before pushing:
- `git describe --match 'v[0-9]*'` -> v2.0.1-458-gd561021 in a full checkout
- `pytest tests/unit/test_run_manifest.py` -> 33 passed in the project
  interpreter, which carries psutil 7.2.2 and full history. That combination
  is why 29.2-03 measured 0 failures locally against CI's 5.
- YAML re-parses; the test job's checkout step is
  {'uses': 'actions/checkout@v4', 'with': {'fetch-depth': 0}}
- `pre-commit run --all-files` (unpinned 4.6.2, isolated venv) -> exit 0, all
  seven hooks Passed, porcelain unchanged across the run

Scope note: this is outside plan 29.2-04, which is complete, and is taken on
the author's explicit ruling to repair the two causes and re-take the board.
The evidence commits held back from PR #3 go up with it, so the board this
re-fires is a new one; 29.2-04's record describes the board at f300c55 and is
left as written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WUVvUaDdEt5rgATdEhEpD3
@tlancaster6
tlancaster6 merged commit 354afd0 into main Aug 27, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant